using ModelContextProtocol.Protocol;
using System.Text.Json;

namespace ModelContextProtocol.Tests.Protocol;

public static class ResourceContentsTests
{
    [Fact]
    public static void TextResourceContents_UnknownArrayProperty_IsIgnored()
    {
        // This test verifies that the ResourceContents.Converter properly skips unknown properties
        // even when they contain complex structures like arrays or objects.
        //
        // In this unexpected JSON, "unknownArray" appears inside text resource contents (where it doesn't belong).
        // The converter should gracefully ignore this unknown property and successfully deserialize
        // the rest of the resource contents.

        const string jsonWithUnknownArray = """
        {
            "uri": "file:///test.txt",
            "mimeType": "text/plain",
            "text": "Test content",
            "unknownArray": [
                {
                    "nested": "value"
                },
                {
                    "another": "object"
                }
            ]
        }
        """;

        var result = JsonSerializer.Deserialize<ResourceContents>(
            jsonWithUnknownArray,
            McpJsonUtilities.DefaultOptions);

        Assert.NotNull(result);
        var textResource = Assert.IsType<TextResourceContents>(result);
        Assert.Equal("file:///test.txt", textResource.Uri);
        Assert.Equal("text/plain", textResource.MimeType);
        Assert.Equal("Test content", textResource.Text);
    }

    [Fact]
    public static void BlobResourceContents_UnknownObjectProperty_IsIgnored()
    {
        // Test that unknown properties with nested objects are properly skipped

        const string jsonWithUnknownObject = """
        {
            "uri": "file:///test.bin",
            "mimeType": "application/octet-stream",
            "blob": "AQIDBA==",
            "unknownObject": {
                "deeply": {
                    "nested": {
                        "value": "should be ignored"
                    }
                }
            }
        }
        """;

        var result = JsonSerializer.Deserialize<ResourceContents>(
            jsonWithUnknownObject,
            McpJsonUtilities.DefaultOptions);

        Assert.NotNull(result);
        var blobResource = Assert.IsType<BlobResourceContents>(result);
        Assert.Equal("file:///test.bin", blobResource.Uri);
        Assert.Equal("application/octet-stream", blobResource.MimeType);
        Assert.Equal("AQIDBA==", blobResource.Blob);
    }

    [Fact]
    public static void TextResourceContents_UnknownMixedProperties_AreIgnored()
    {
        // Test multiple unknown properties with different types

        const string jsonWithMixedUnknown = """
        {
            "uri": "test://resource",
            "text": "content",
            "unknownString": "value",
            "unknownNumber": 42,
            "unknownArray": [1, 2, 3],
            "unknownObject": {"key": "value"},
            "unknownBool": true,
            "unknownNull": null
        }
        """;

        var result = JsonSerializer.Deserialize<ResourceContents>(
            jsonWithMixedUnknown,
            McpJsonUtilities.DefaultOptions);

        Assert.NotNull(result);
        var textResource = Assert.IsType<TextResourceContents>(result);
        Assert.Equal("test://resource", textResource.Uri);
        Assert.Equal("content", textResource.Text);
    }

    [Fact]
    public static void BlobResourceContents_UnknownNestedArrays_AreIgnored()
    {
        // Test complex unknown properties with arrays of objects

        const string jsonWithNestedArrays = """
        {
            "uri": "blob://test",
            "blob": "SGVsbG8=",
            "mimeType": "application/custom",
            "unknownComplex": [
                {
                    "nested": [
                        {"deep": "value1"},
                        {"deep": "value2"}
                    ]
                },
                {
                    "nested": [
                        {"deep": "value3"}
                    ]
                }
            ]
        }
        """;

        var result = JsonSerializer.Deserialize<ResourceContents>(
            jsonWithNestedArrays,
            McpJsonUtilities.DefaultOptions);

        Assert.NotNull(result);
        var blobResource = Assert.IsType<BlobResourceContents>(result);
        Assert.Equal("blob://test", blobResource.Uri);
        Assert.Equal("SGVsbG8=", blobResource.Blob);
        Assert.Equal("application/custom", blobResource.MimeType);
    }

    [Fact]
    public static void TextResourceContents_MultipleUnknownProperties_AllIgnored()
    {
        // Test that multiple unknown properties are all properly skipped

        const string jsonWithMultipleUnknown = """
        {
            "uri": "file:///test",
            "unknownOne": {"a": 1},
            "text": "Test text",
            "unknownTwo": [1, 2, 3],
            "mimeType": "text/plain",
            "unknownThree": {"b": {"c": "d"}}
        }
        """;

        var result = JsonSerializer.Deserialize<ResourceContents>(
            jsonWithMultipleUnknown,
            McpJsonUtilities.DefaultOptions);

        Assert.NotNull(result);
        var textResource = Assert.IsType<TextResourceContents>(result);
        Assert.Equal("file:///test", textResource.Uri);
        Assert.Equal("Test text", textResource.Text);
        Assert.Equal("text/plain", textResource.MimeType);
    }

    [Fact]
    public static void BlobResourceContents_UnknownArrayOfArrays_IsIgnored()
    {
        // Test deeply nested array structures in unknown properties

        const string jsonWithArrayOfArrays = """
        {
            "uri": "http://example.com/blob",
            "blob": "Zm9v",
            "unknownNested": [
                [
                    [1, 2, 3],
                    [4, 5, 6]
                ],
                [
                    [7, 8, 9]
                ]
            ]
        }
        """;

        var result = JsonSerializer.Deserialize<ResourceContents>(
            jsonWithArrayOfArrays,
            McpJsonUtilities.DefaultOptions);

        Assert.NotNull(result);
        var blobResource = Assert.IsType<BlobResourceContents>(result);
        Assert.Equal("http://example.com/blob", blobResource.Uri);
        Assert.Equal("Zm9v", blobResource.Blob);
    }

    [Fact]
    public static void TextResourceContents_EmptyUnknownArray_IsIgnored()
    {
        // Test empty arrays in unknown properties

        const string jsonWithEmptyArray = """
        {
            "uri": "test://text",
            "text": "content",
            "unknownEmpty": []
        }
        """;

        var result = JsonSerializer.Deserialize<ResourceContents>(
            jsonWithEmptyArray,
            McpJsonUtilities.DefaultOptions);

        Assert.NotNull(result);
        var textResource = Assert.IsType<TextResourceContents>(result);
        Assert.Equal("test://text", textResource.Uri);
        Assert.Equal("content", textResource.Text);
    }

    [Fact]
    public static void BlobResourceContents_EmptyUnknownObject_IsIgnored()
    {
        // Test empty objects in unknown properties

        const string jsonWithEmptyObject = """
        {
            "uri": "test://blob",
            "blob": "YmFy",
            "unknownEmpty": {}
        }
        """;

        var result = JsonSerializer.Deserialize<ResourceContents>(
            jsonWithEmptyObject,
            McpJsonUtilities.DefaultOptions);

        Assert.NotNull(result);
        var blobResource = Assert.IsType<BlobResourceContents>(result);
        Assert.Equal("test://blob", blobResource.Uri);
        Assert.Equal("YmFy", blobResource.Blob);
    }

    [Fact]
    public static void TextResourceContents_UnknownPropertiesBetweenRequired_AreIgnored()
    {
        // Test unknown properties interspersed with required ones

        const string jsonWithInterspersedUnknown = """
        {
            "unknownFirst": {"x": 1},
            "uri": "file:///document.txt",
            "unknownSecond": [1, 2],
            "text": "Document content",
            "unknownThird": {"nested": {"value": true}},
            "mimeType": "text/plain"
        }
        """;

        var result = JsonSerializer.Deserialize<ResourceContents>(
            jsonWithInterspersedUnknown,
            McpJsonUtilities.DefaultOptions);

        Assert.NotNull(result);
        var textResource = Assert.IsType<TextResourceContents>(result);
        Assert.Equal("file:///document.txt", textResource.Uri);
        Assert.Equal("Document content", textResource.Text);
        Assert.Equal("text/plain", textResource.MimeType);
    }

    [Fact]
    public static void BlobResourceContents_VeryDeeplyNestedUnknown_IsIgnored()
    {
        // Test very deeply nested structures in unknown properties

        const string jsonWithVeryDeepNesting = """
        {
            "uri": "deep://blob",
            "blob": "ZGVlcA==",
            "unknownDeep": {
                "level1": {
                    "level2": {
                        "level3": {
                            "level4": {
                                "level5": {
                                    "value": "deep"
                                }
                            }
                        }
                    }
                }
            }
        }
        """;

        var result = JsonSerializer.Deserialize<ResourceContents>(
            jsonWithVeryDeepNesting,
            McpJsonUtilities.DefaultOptions);

        Assert.NotNull(result);
        var blobResource = Assert.IsType<BlobResourceContents>(result);
        Assert.Equal("deep://blob", blobResource.Uri);
        Assert.Equal("ZGVlcA==", blobResource.Blob);
    }

    [Fact]
    public static void TextResourceContents_WithMeta_UnknownPropertiesIgnored()
    {
        // Test that _meta property works correctly alongside unknown properties

        const string jsonWithMetaAndUnknown = """
        {
            "uri": "test://meta",
            "text": "content with meta",
            "unknownProp": {"data": "ignored"},
            "_meta": {
                "customField": "metaValue"
            }
        }
        """;

        var result = JsonSerializer.Deserialize<ResourceContents>(
            jsonWithMetaAndUnknown,
            McpJsonUtilities.DefaultOptions);

        Assert.NotNull(result);
        var textResource = Assert.IsType<TextResourceContents>(result);
        Assert.Equal("test://meta", textResource.Uri);
        Assert.Equal("content with meta", textResource.Text);
        Assert.NotNull(textResource.Meta);
        Assert.True(textResource.Meta.ContainsKey("customField"));
    }

    [Fact]
    public static void TextResourceContents_SerializationRoundTrip_PreservesKnownProperties()
    {
        // Test that serialization/deserialization preserves known properties

        var original = new TextResourceContents
        {
            Uri = "file:///test.txt",
            MimeType = "text/plain",
            Text = "Test content"
        };

        var json = JsonSerializer.Serialize<ResourceContents>(original, McpJsonUtilities.DefaultOptions);
        var deserialized = JsonSerializer.Deserialize<ResourceContents>(json, McpJsonUtilities.DefaultOptions);

        Assert.NotNull(deserialized);
        var textResource = Assert.IsType<TextResourceContents>(deserialized);
        Assert.Equal(original.Uri, textResource.Uri);
        Assert.Equal(original.MimeType, textResource.MimeType);
        Assert.Equal(original.Text, textResource.Text);
    }

    [Fact]
    public static void BlobResourceContents_SerializationRoundTrip_PreservesKnownProperties()
    {
        // Test that serialization/deserialization preserves known properties

        var original = new BlobResourceContents
        {
            Uri = "file:///test.bin",
            MimeType = "application/octet-stream",
            Blob = "AQIDBA=="
        };

        var json = JsonSerializer.Serialize<ResourceContents>(original, McpJsonUtilities.DefaultOptions);
        var deserialized = JsonSerializer.Deserialize<ResourceContents>(json, McpJsonUtilities.DefaultOptions);

        Assert.NotNull(deserialized);
        var blobResource = Assert.IsType<BlobResourceContents>(deserialized);
        Assert.Equal(original.Uri, blobResource.Uri);
        Assert.Equal(original.MimeType, blobResource.MimeType);
        Assert.Equal(original.Blob, blobResource.Blob);
    }

    [Fact]
    public static void ResourceContents_MissingBothTextAndBlob_ReturnsNull()
    {
        // Test that missing both text and blob properties returns null

        const string jsonWithoutContent = """
        {
            "uri": "test://empty",
            "mimeType": "application/unknown"
        }
        """;

        var result = JsonSerializer.Deserialize<ResourceContents>(
            jsonWithoutContent,
            McpJsonUtilities.DefaultOptions);

        Assert.Null(result);
    }

    [Fact]
    public static void ResourceContents_WithBothTextAndBlob_PrefersBlob()
    {
        // Test that when both text and blob are present, blob takes precedence

        const string jsonWithBoth = """
        {
            "uri": "test://both",
            "text": "text content",
            "blob": "YmxvYg=="
        }
        """;

        var result = JsonSerializer.Deserialize<ResourceContents>(
            jsonWithBoth,
            McpJsonUtilities.DefaultOptions);

        Assert.NotNull(result);
        var blobResource = Assert.IsType<BlobResourceContents>(result);
        Assert.Equal("test://both", blobResource.Uri);
        Assert.Equal("YmxvYg==", blobResource.Blob);
    }

    [Fact]
    public static void TextResourceContents_MissingUri_UsesEmptyString()
    {
        // Test that missing uri defaults to empty string

        const string jsonWithoutUri = """
        {
            "text": "content without uri"
        }
        """;

        var result = JsonSerializer.Deserialize<ResourceContents>(
            jsonWithoutUri,
            McpJsonUtilities.DefaultOptions);

        Assert.NotNull(result);
        var textResource = Assert.IsType<TextResourceContents>(result);
        Assert.Equal(string.Empty, textResource.Uri);
        Assert.Equal("content without uri", textResource.Text);
    }

    [Fact]
    public static void BlobResourceContents_MissingUri_UsesEmptyString()
    {
        // Test that missing uri defaults to empty string

        const string jsonWithoutUri = """
        {
            "blob": "YmxvYg=="
        }
        """;

        var result = JsonSerializer.Deserialize<ResourceContents>(
            jsonWithoutUri,
            McpJsonUtilities.DefaultOptions);

        Assert.NotNull(result);
        var blobResource = Assert.IsType<BlobResourceContents>(result);
        Assert.Equal(string.Empty, blobResource.Uri);
        Assert.Equal("YmxvYg==", blobResource.Blob);
    }
}
